package com.touzhijia.authority.config;

import com.touzhijia.authority.domain.dto.MessageRes;
import com.touzhijia.authority.exception.BusinessException;
import com.touzhijia.authority.exception.ErrorCode;
import com.touzhijia.authority.utils.DateTimeUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.ui.Model;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.support.MissingServletRequestPartException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ValidationException;
import java.beans.PropertyEditorSupport;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * 统一的异常处理、数据绑定
 * <p>
 * 作者：lizw <br/>
 * 创建时间：2017/6/2 11:05 <br/>
 */
@Slf4j
@ControllerAdvice
public class ControllerGlobalConfig {
    /**
     * 此处将键值对添加到全局，注解了@RequestMapping的方法都可以获得此键值对
     */
    @ModelAttribute
    public void addAttributes(Model model, HttpServletRequest request, HttpServletResponse response) {
        // dto.addAttribute("key", "value");
    }

    @ResponseBody
    @ExceptionHandler(value = BusinessException.class)
    public MessageRes defaultErrorHandler(HttpServletRequest req, HttpServletResponse res, BusinessException e) {
        log.error("[全局的异常处理] - ", e);
        if (Objects.equals(e.getErrorCode(), ErrorCode.INTERNAL_SERVER_ERROR.getCode())) {
            res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        } else {
            res.setStatus(HttpStatus.BAD_REQUEST.value());
        }
        return newMessageRes(req, res, e);
    }

    @ResponseBody
    @ExceptionHandler(value = Throwable.class)
    public MessageRes defaultErrorHandler(HttpServletRequest req, HttpServletResponse res, Throwable e) {
        log.error("[全局的异常处理] - ", e);
        String message = null;
        ErrorCode errorCode;
        if (e instanceof MethodArgumentNotValidException) {
            res.setStatus(HttpStatus.BAD_REQUEST.value());
            errorCode = ErrorCode.INVALID_PARAMETER;
            message = getVerifyMsg(((MethodArgumentNotValidException) e).getBindingResult());
            log.info("请求参数校验失败 [{}]", message);
        } else if (e instanceof BindException) {
            res.setStatus(HttpStatus.BAD_REQUEST.value());
            errorCode = ErrorCode.INVALID_PARAMETER;
            message = getVerifyMsg(((BindException) e).getBindingResult());
            log.info("请求参数校验失败 [{}]", message);
        } else if (e instanceof ValidationException) {
            res.setStatus(HttpStatus.BAD_REQUEST.value());
            errorCode = ErrorCode.INVALID_PARAMETER;
        } else if (e instanceof HttpMessageConversionException) {
            res.setStatus(HttpStatus.BAD_REQUEST.value());
            errorCode = ErrorCode.PARSE_JSON_ERROR;
        } else if (e instanceof HttpRequestMethodNotSupportedException) {
            res.setStatus(HttpStatus.BAD_REQUEST.value());
            errorCode = ErrorCode.REQUEST_NOT_SUPPORTED;
        } else if (e instanceof ServletRequestBindingException) {
            res.setStatus(HttpStatus.BAD_REQUEST.value());
            errorCode = ErrorCode.MISSING_PARAMETER;
        } else if (e instanceof MissingServletRequestPartException) {
            res.setStatus(HttpStatus.BAD_REQUEST.value());
            errorCode = ErrorCode.MISSING_PARAMETER;
        } else if (e instanceof AccessDeniedException) {
            res.setStatus(HttpStatus.FORBIDDEN.value());
            errorCode = ErrorCode.FORBIDDEN;
        } else {
            res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            errorCode = ErrorCode.INTERNAL_SERVER_ERROR;
        }
        return newMessageRes(req, res, e, errorCode, message);
    }

    private MessageRes newMessageRes(HttpServletRequest req, HttpServletResponse res, Throwable e, ErrorCode errorCode, String message) {
        MessageRes messageRes = new MessageRes();
        messageRes.setPath(req.getRequestURI());
        messageRes.setStatus(res.getStatus());
        if (message != null) {
            messageRes.setMessage(message);
        } else if (errorCode != null) {
            messageRes.setMessage(errorCode.getMessage());
        } else {
            messageRes.setMessage(e.getMessage());
        }
        if (errorCode != null) {
            messageRes.setError(errorCode.getMessage());
        } else {
            messageRes.setError(e.getMessage());
        }
        messageRes.setException(e.getClass().getName());
        messageRes.setTimestamp(System.currentTimeMillis());
        return messageRes;
    }


    private MessageRes newMessageRes(HttpServletRequest req, HttpServletResponse res, Throwable e) {
        MessageRes messageRes = new MessageRes();
        messageRes.setPath(req.getRequestURI());
        messageRes.setStatus(res.getStatus());
        messageRes.setMessage(e.getMessage());
        messageRes.setError(e.getMessage());
        messageRes.setException(e.getClass().getName());
        messageRes.setTimestamp(System.currentTimeMillis());
        return messageRes;
    }

    private String getVerifyMsg(BindingResult bindingResult) {
        List<FieldError> validError = bindingResult.getFieldErrors();
        StringBuilder sb = new StringBuilder();
        for (FieldError fieldError : validError) {
            String filed = fieldError.getField();
            String errorMessage = fieldError.getDefaultMessage();
            if (sb.length() > 0) {
                break;
                // sb.append(" | ");
            }
            sb.append(filed).append(":").append(errorMessage);
        }
        return sb.toString();
    }

    /**
     * 初始化数据绑定,每个请求都会进来这个方法,每次WebDataBinder binder都会是一个新对象<br/>
     * 1.将所有传递进来的String进行HTML编码，防止XSS攻击 <br/>
     * 2.将字段中Date类型转换为String类型<br/>
     */
    @InitBinder
    public void initBinder(WebDataBinder binder, HttpServletRequest request, HttpServletResponse response) {
        // Date 类型转换
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                setValue(DateTimeUtils.parseDate(text));
            }
        });

        // String 去掉首尾空格
        binder.registerCustomEditor(String.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                setValue(StringUtils.trim(text));
            }
        });
    }
}
